package parser;

/*
 * Parser for update
 * Takes in command from GUI.Main, and call appropriate method in update
 * 
 */

import java.util.Calendar;
import java.util.GregorianCalendar;

import logger.Logger;
import logic.DateTime;
import logic.Display;
import logic.Update;
import memory.Memory;

public class ParserUpdate extends ParserMain{


	private static final String ATTRIBUTE_DATE = "date";
	private static final String ATTRIBUTE_ENDDATE = "enddate";
	private static final String ATTRIBUTE_STARTDATE = "startdate";
	private static final String ATTRIBUTE_ENDTIME = "endtime";
	private static final String ATTRIBUTE_STARTTIME = "starttime";
	private static final String ATTRIBUTE_TIME = "time";
	private static final String ATTRIBUTE_DESCRIPTION = "description";
	private static final String ATTRIBUTE_TASKNAME = "taskname";
	private static final String ATTRIBUTE_NAME = "name";

	private static final String COMMA = ",";
	private static final String SPACE = " ";

	private static final String NEXT = "next";
	private static final String THIS = "this";

	private static final String MESSAGE_UPDATING_DIRECTLY = "Updating directly:";
	private static final String MESSAGE_UPDATE_AFTER_SEARCH = "Update after search";

	private static final String ERROR_INVALID_INPUT_TIME = "Invalid input time";
	private static final String ERROR_INVALD_INPUT_DATE = "Invald input date";
	private static final String ERROR_INVALID_TYPE = "You may only update after search or directly with the correct taskname";
	private static final String ERROR_INVALID_ATTRIBUTE_NAME = "Error! Invalid attribute name.";
	private static final String ERROR_COMMAND_AVAILABLE_ONLY_IN_LIST_VIEW = "Command available only in list view!";


	//Different type of Update
	private static final int UPDATE_DIRECTLY = 31;
	private static final int UPDATE_AFTER_SEARCH = 32;
	private static final int UPDATE_IN_DETAILED_VIEW = 33;

	private static final int UPDATE_NAME = 34;
	private static final int UPDATE_DESCRIPTION = 35;
	private static final int UPDATE_START_TIME = 36;
	private static final int UPDATE_END_TIME = 37;
	private static final int UPDATE_TIME = 38;
	private static final int UPDATE_START_DATE = 39;
	private static final int UPDATE_END_DATE = 40;
	private static final int UPDATE_DATE = 41;

	private static final int MONDAY = 0;
	private static final int SUNDAY = 6;
	private static final int YESTERDAY = 10;
	private static final int TODAY = 11;
	private static final int TOMORROW = 12;
	private static final int NOT_SET = -1;

	private static String attribute_type;
	private static int attribute = INVALID_INT;
	private static int index = INVALID_INT;
	private static Calendar dateTime;

	//@author A0085082E
	/*
	 * Run update as called by GUI.Main
	 * Checks if command string is empty, if prompt the user with an error message.
	 * Otherwise calls update
	 */
	public static void run(String command, Memory memory){
		initVar();
		setCommand(command);
		if(isUselessCommand(command)){
			generalError();
		}else{
			update(memory);
		}

	}

	//@author A0085082E
	/*
	 * Initialise variables
	 */
	private static void initVar(){
		setProper(false);
		setTaskName(null);
		setDescription(null);
		dateTime=  new GregorianCalendar();
		setType(NOT_SET);
	}

	//@author A0085082E
	/*
	 * Takes in command and memory
	 * Set command type and check that attributes to be updated are available
	 * If not sent an error to user
	 */
	private static void update(Memory memory){

		String detailsToBeUpdated = setUpdateType();

		switch(type){
		case UPDATE_DIRECTLY:
			if(attributesForUpdatedDirectAreSet(detailsToBeUpdated)){
				System.out.println(MESSAGE_UPDATING_DIRECTLY);
				Update.updateExact(taskName, attribute, dateTime, detailsToBeUpdated.trim(), memory);
			}
			break;

		case UPDATE_AFTER_SEARCH:
			if (Display.listView == null) {
				System.out.println(ERROR_COMMAND_AVAILABLE_ONLY_IN_LIST_VIEW);
			}else if(attributesForUpdatedAfterSearchAreSet(detailsToBeUpdated)){
				assert Display.listView != null;
				System.out.println(MESSAGE_UPDATE_AFTER_SEARCH);
				Update.updateAfterSearch(taskName, attribute, dateTime, detailsToBeUpdated, index-1, memory);
			}
			break;

		case UPDATE_IN_DETAILED_VIEW:
			if(attributesForUpdatedDetailedAreSet(detailsToBeUpdated)){
				Update.updateInDetailed(taskName, attribute, dateTime, detailsToBeUpdated.trim(), memory);
			}
			break;

		default:
			error();
		}

	}

	//@author A0085082E
	/*
	 * Set update type and return the details that user want to update
	 * If type is invalid or user input is not in the correct format, null is returned
	 */
	private static String setUpdateType(){
		String[] details = command.split(SPACE);

		if(Display.detailedView != null && isValidAttribute(details[0].trim())>0){
			setType(UPDATE_IN_DETAILED_VIEW);
			return updateInDetailedView();
		}else if(command.contains(COMMA)){
			setType(UPDATE_DIRECTLY);
			return updateDirect();	
		}else if(details.length >=3){
			setType(UPDATE_AFTER_SEARCH);
			return updateAfterSearch(details);
		}

		return null;
	}

	//@author A0085082E
	/*
	 * Set variables required to update directly
	 * Return the details to be updated as a string
	 */
	private static String updateDirect() {
		String[] details = command.split(COMMA);
		String detailsToBeUpdated = details[1].trim();
		setTaskName(details[0].trim());

		String[] update_details = detailsToBeUpdated.split(SPACE);

		attribute_type = update_details[0].trim();
		attribute = isValidAttribute(attribute_type);

		detailsToBeUpdated = detailsToBeUpdated.substring(attribute_type.length()).trim();

		return detailsToBeUpdated;
	}

	//@author A0085082E
	/*
	 * Set variables required to update search
	 * Return the details to be updated as a string
	 */
	private static String updateAfterSearch(String[] details) {
		String detailsToBeUpdated = null;
		if(isNumeric(details[0].trim())){
			index = Integer.parseInt(details[0].trim());
			attribute_type = details[1].trim();
			attribute = isValidAttribute(attribute_type);

			detailsToBeUpdated = command.substring(command.indexOf(attribute_type)+attribute_type.length()).trim();
		}
		return detailsToBeUpdated;
	}

	//@author A0085082E
	/*
	 * Set variables required to update detailedView
	 * Return the details to be updated as a string
	 */
	private static String updateInDetailedView() {
		String[] details = command.split(SPACE);
		String detailsToBeUpdated = null;

		attribute_type = details[0].trim();
		attribute = isValidAttribute(attribute_type);

		detailsToBeUpdated = command.substring(command.indexOf(attribute_type)+attribute_type.length()).trim();

		return detailsToBeUpdated;
	}

	//@author A0085082E
	/*
	 * Check that the attribute that user wants to update is valid'
	 * It is matches, return an integer representing the attribute
	 * Otherwise generate an error message
	 */
	private static int isValidAttribute(String temp){
		switch(temp.toLowerCase()){
		case ATTRIBUTE_NAME: case ATTRIBUTE_TASKNAME:
			return UPDATE_NAME;
		case ATTRIBUTE_DESCRIPTION:
			return UPDATE_DESCRIPTION;
		case ATTRIBUTE_TIME: 
			return UPDATE_TIME;
		case ATTRIBUTE_STARTTIME:
			return UPDATE_START_TIME;
		case ATTRIBUTE_ENDTIME:
			return UPDATE_END_TIME;
		case ATTRIBUTE_STARTDATE:
			return UPDATE_START_DATE;
		case ATTRIBUTE_ENDDATE:
			return UPDATE_END_DATE;
		case ATTRIBUTE_DATE:
			return UPDATE_DATE;
		default:
			System.out.println(ERROR_INVALID_ATTRIBUTE_NAME);
			Logger.add(LOGGER_ERROR + ERROR_INVALID_ATTRIBUTE_NAME);
			return NOT_SET;

		}
	}

	//@author A0085082E
	/*
	 * Set details to be updated to the variable
	 * If details to be updated is empty, or the details is invalid, return false
	 * If details to be updated can be set properly to the correct variable, return true
	 */
	private static boolean setAttribute(int attribute, String detailsToBeUpdated){
		if(detailsToBeUpdated.isEmpty()){
			generalError();
			return false;
		}else{

			switch(attribute){

			case UPDATE_DESCRIPTION: case UPDATE_NAME: 
				return true;

			case UPDATE_TIME: case UPDATE_START_TIME: case UPDATE_END_TIME:
				return checkDetailsToUpdateTime(detailsToBeUpdated);

			case  UPDATE_START_DATE: case  UPDATE_END_DATE: case  UPDATE_DATE:
				String[] details = detailsToBeUpdated.trim().split(SPACE);

				return checkDetailsToUpdateDate(detailsToBeUpdated, details);

			default:
				System.out.println(ERROR_INVALID_ATTRIBUTE_NAME);
				Logger.add(LOGGER_ERROR + ERROR_INVALID_ATTRIBUTE_NAME);
				return false;
			}
		}
	}

	//@author A0085082E
	/*
	 * Set time
	 * If time can be set return true
	 */
	private static boolean checkDetailsToUpdateTime(String detailsToBeUpdated) {
		if(setTime(detailsToBeUpdated, dateTime)){
			return true;
		}else{
			System.out.println(ERROR_INVALID_INPUT_TIME);
			Logger.add(LOGGER_ERROR + ERROR_INVALID_INPUT_TIME);
			return false;
		}
	}

	//@author A0085082E
	/*
	 * Set date
	 * If date is set, returns true
	 */
	private static boolean checkDetailsToUpdateDate(String detailsToBeUpdated,
			String[] details) {
		if(details.length == 1 && (DateTime.isNonNumericDateWeekDay(details[0])||DateTime.isNonNumericDateNotWeekDay(details[0]))){
			dateTime = setNonNumericDate(details[0], dateTime);
			return true;
		}else if(details.length == 2 && DateTime.isNonNumericDateWeekDay(details[1]) && details[0].equals(THIS)){
			dateTime = setNonNumericDate(details[0], dateTime);
			return true;
		}else if(details.length == 2 && DateTime.isNonNumericDateWeekDay(details[1]) && details[0].equals(NEXT)){
			dateTime = setNonNumericDateForNextWeek(details[1], dateTime);
			return true;
		}else if(setDate(detailsToBeUpdated, dateTime)){
			return true;
		}else{
			System.out.println(ERROR_INVALD_INPUT_DATE);
			Logger.add(LOGGER_ERROR + ERROR_INVALD_INPUT_DATE);
			return false;
		}
	}

	//@author A0085082E
	/*
	 * To generate error message for two cases:
	 * 1. Type not set
	 * 2. General error
	 */
	private static void error(){
		if(type == INVALID_INT){
			System.out.println(ERROR_INVALID_TYPE);
			Logger.add(LOGGER_ERROR + ERROR_INVALID_TYPE);
			
		}else{
			generalError();
		}
	}

	//@author A0085082E
	/*
	 * Set non-numeric date to calendar
	 */
	private static Calendar setNonNumericDate(String details, Calendar cal){
		int date = DateTime.isDayOfWeek(details);
		int today = DateTime.getDayOfTheWeek(cal);
		if(date != TODAY ){
			if(date>= MONDAY && date<= SUNDAY){
				cal = DateTime.addNumberOfDays(cal, (date - today + 7) % 7);
			}else if(date == TOMORROW){
				cal = DateTime.tomorrow(cal);
			}else if(date == YESTERDAY){
				cal = DateTime.addNumberOfDays(cal, -1);
			}
		}

		return cal;
	}

	//@author A0085082E
	/*
	 * Set date for week after
	 */
	private static Calendar setNonNumericDateForNextWeek(String details, Calendar cal){
		cal = setNonNumericDate(details,cal);
		cal = DateTime.nextWeek(cal);

		return cal;
	}

	//@author A0085082E
	private static boolean attributesForUpdatedDetailedAreSet(String detailsToBeUpdated){
		return detailsToBeUpdated != null && attribute != NOT_SET && setAttribute(attribute, detailsToBeUpdated);
	}

	//@author A0085082E
	private static boolean attributesForUpdatedDirectAreSet(String detailsToBeUpdated){
		return detailsToBeUpdated != null && attribute != NOT_SET && setAttribute(attribute, detailsToBeUpdated);
	}

	//@author A0085082E
	private static boolean attributesForUpdatedAfterSearchAreSet(String detailsToBeUpdated){
		return detailsToBeUpdated != null && setAttribute(attribute, detailsToBeUpdated) && attribute != NOT_SET && index >0;
	}


}
